namespace AgentJohnson
{
  using System;
  using JetBrains.Annotations;
  using JetBrains.ReSharper.Feature.Services.CSharp.Bulbs;
  using Resharper.Abstractions.Actions;
  using Resharper.Abstractions.CodeDom.TypeMembers.Methods;
  using Resharper.Abstractions.CodeDom.TypeMembers.Properties;
  using Resharper.Abstractions.CodeDom.Types.Classes;

  /// <summary>Defines the <see cref="MakeAbstract"/> class.</summary>
  [ContextAction("Make virtual member abstract [Agent Johnson]", "Converts a virtual method to an abstract method.")]
  public class MakeAbstract : ContextActionBase
  {
    #region Constructors and Destructors

    /// <summary>Initializes a new instance of the <see cref="MakeAbstract"/> class.</summary>
    /// <param name="provider">The provider.</param>
    public MakeAbstract([NotNull] ICSharpContextActionDataProvider provider) : base(provider)
    {
    }

    #endregion

    #region Methods

    /// <summary>Determines whether this instance can execute the specified action context.</summary>
    /// <param name="context">The action context.</param>
    /// <returns><c>true</c> if this instance can execute the specified action context; otherwise, <c>false</c>.</returns>
    protected override bool CanExecute(CanExecuteContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      return this.GetModel(context) != null;
    }

    /// <summary>Executes the action.</summary>
    /// <param name="context">The execute context.</param>
    protected override void Execute(ExecuteContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      var model = this.GetModel(context);
      if (model == null)
      {
        return;
      }

      var function = model.Method;
      if (function != null)
      {
        function.IsAbstract = true;
        function.IsVirtual = false;
        function.Body = null;
      }

      var property = model.Property;
      if (property != null)
      {
        property.IsAbstract = true;
        property.IsVirtual = false;

        foreach (var accessor in property.Accessors)
        {
          accessor.Body = null;
        }
      }

      model.Class.IsAbstract = true;
    }

    /// <summary>Gets the text.</summary>
    /// <returns>Returns the text.</returns>
    protected override string GetText()
    {
      return "Make abstract [Agent Johnson]";
    }

    /// <summary>Gets the model.</summary>
    /// <param name="context">The context.</param>
    /// <returns>Returns the model.</returns>
    private Model GetModel(ProviderContext context)
    {
      var text = context.GetSelectedText();
      if (text != "virtual")
      {
        return null;
      }

      var function = context.GetContainingMethod();
      if (function != null && !function.IsVirtual)
      {
        return null;
      }

      var property = context.GetContainingProperty();
      if (property != null && !property.IsVirtual)
      {
        return null;
      }

      if (function == null && property == null)
      {
        return null;
      }

      var @class = context.GetContainingClass();
      if (@class == null)
      {
        return null;
      }

      return new Model
      {
        Class = @class,
        Method = function,
        Property = property
      };
    }

    #endregion

    /// <summary>Defines the <see cref="Model"/> class.</summary>
    private class Model
    {
      #region Properties

      /// <summary>
      /// Gets or sets the class.
      /// </summary>
      /// <value>The class.</value>
      [NotNull]
      public Class Class { get; set; }

      /// <summary>
      /// Gets or sets the function.
      /// </summary>
      /// <value>The function.</value>
      [CanBeNull]
      public Method Method { get; set; }

      /// <summary>
      /// Gets or sets the property.
      /// </summary>
      /// <value>The property.</value>
      [CanBeNull]
      public Property Property { get; set; }

      #endregion
    }
  }
}